package com.snlab;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;

public class Device {
    private String name;
    private BDDEngine gEngine;
    public BDDEngine bddEngine;
    private HashMap<String, Port> nameToPort;
    private ArrayList<Rule> rules;

    public Device(String name, BDDEngine gEngine) {
        this.name = name;
        this.gEngine = gEngine;
        this.bddEngine = new BDDEngine();
        this.nameToPort = new HashMap<>();
        this.addPort("default");
        this.rules = new ArrayList<>();
        Rule defaultRule = new Rule(this, 0, 0, this.getPort("default"));
        defaultRule.setHit(1);
        this.rules.add(defaultRule);
    }

    public Port getPort(String name) {
        return this.nameToPort.get(name);
    }

    public Port addPort(String name) {
        Port port = new Port(name);
        port.setDevice(this);
        this.nameToPort.put(name, port);
        return port;
    }

    public HashSet<Change> identifyChangesInsert(Rule rule) {
        HashSet<Change> changes = new HashSet<>();
        Collections.sort(this.rules);
        for (Rule r1 : this.rules) {
            if (r1.getPrefix() > rule.getPrefix() && bddEngine.bdd.and(r1.getHit(), rule.getHit()) != 0) {
                int nvar = bddEngine.bdd.ref(bddEngine.bdd.not(r1.getHit()));
                rule.setHit(bddEngine.bdd.ref(bddEngine.bdd.and(rule.getHit(), nvar)));
                bddEngine.bdd.deref(nvar);
            }
            if (r1.getPrefix() < rule.getPrefix() && bddEngine.bdd.and(r1.getHit(), rule.getHit()) != 0) {
                if (r1.getOutPort() != rule.getOutPort()) {
                    int hit = bddEngine.bdd.ref(bddEngine.bdd.and(rule.getHit(), r1.getHit()));
                    Change change = new Change(hit, r1.getOutPort(), rule.getOutPort());
                    changes.add(change);
                }
                int nh = bddEngine.bdd.ref(bddEngine.bdd.and(r1.getHit(), bddEngine.bdd.ref(bddEngine.bdd.not(rule.getHit()))));
                r1.setHit(nh);
            }
        }
        this.rules.add(rule);
        return changes;
    }

    public HashSet<Change> identifyChangesDelete(Rule rule) {
        HashSet<Change> changes = new HashSet<>();
        this.rules.remove(rule);
        int hit = rule.getHit();

        Collections.sort(this.rules);
        for (Rule r1 : this.rules) {
            int intersection = bddEngine.bdd.ref(bddEngine.bdd.and(hit, bddEngine.encodeIpv4(r1.getMatch(), r1.getPrefix())));
            if (r1.getPriority() < rule.getPriority() && intersection != 0) {
                if (rule.getOutPort() != r1.getOutPort()) {
                    Change change = new Change(intersection, rule.getOutPort(), r1.getOutPort());
                    changes.add(change);
                }
                int nh = bddEngine.bdd.ref(bddEngine.bdd.or(r1.getHit(), intersection));
                r1.setHit(nh);
                hit = bddEngine.bdd.ref(bddEngine.bdd.and(hit, bddEngine.bdd.not(intersection)));
            }
            bddEngine.bdd.deref(intersection);
        }
        
        return changes;
    }
    
}
